#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>


#include "teex/util.h"
#include "teex/error.h"
#include "teex/file.h"
#include "teex/debug.h"
#include "teex/base64.h"


teex_status_t fileToBuf(teexFile *file, char **buf, int *size)
{
	int i = 0;
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;

	if (file == NULL || buf == NULL || size == NULL)
		return TEEX_ERROR_NULL_ARG;

	if (file->type == TEEX_DIR) {
        
		for (i = 0; i < file->body.dir.fileNumber; i++) {
			if ((retval = fileToBuf(file->body.dir.files[i], buf, size)) != TEEX_SUCCESS)
				return retval;

        }

    } else if (file->type == TEEX_FILE) {

        *buf = (char*)realloc(*buf, *size + file->body.file.size); 
        memcpy(*buf + *size, file->body.file.text, file->body.file.size);
        *size += file->body.file.size;

    } else {
        return TEEX_ERROR_INTERNAL;
    }	

	return TEEX_SUCCESS;
}

teex_status_t calFileHash(teexFile *file, char **fileHash)
{
	if (file == NULL || fileHash == NULL)
		return TEEX_ERROR_NULL_ARG;
	
	char *fileBuf = NULL;
	int size = 0;
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;

	if ((retval = fileToBuf(file, &fileBuf, &size)) != TEEX_SUCCESS) {
		return retval;
	}

	*fileHash = sha256AndToHexStr(fileBuf, size);

	return TEEX_SUCCESS;
}


teex_status_t fileToJson(teexFile *file, cJSON *fileJson)
{
	int i = 0;
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;
	char *bodyBase64 = NULL;
	cJSON *fileArray = NULL, *fileArrayItem = NULL;


	if (file == NULL || fileJson == NULL) {
		return TEEX_ERROR_NULL_ARG;
	}

		
	if (file->type == TEEX_DIR) {
		
		/* handle the dir */
		cJSON_AddStringToObject(fileJson, "name", file->name);
		cJSON_AddStringToObject(fileJson, "type", "DIR");

		if ((fileArray = cJSON_CreateArray()) == NULL) {
			return TEEX_ERROR_OUT_MEMORY;
		}

		for (i = 0; i < file->body.dir.fileNumber; i++) {
			if ((fileArrayItem = cJSON_CreateObject()) == NULL) {
				return TEEX_ERROR_OUT_MEMORY;
			}

			if ((retval = fileToJson(file->body.dir.files[i], fileArrayItem)) != TEEX_SUCCESS)
				return retval;

			cJSON_AddItemToArray(fileArray, fileArrayItem);
		}

		cJSON_AddItemToObject(fileJson, "files", fileArray);

		return TEEX_SUCCESS;

	} else if (file->type == TEEX_FILE) {
		
		cJSON_AddStringToObject(fileJson, "name", file->name);
		cJSON_AddStringToObject(fileJson, "type", "FILE");
	


		bodyBase64 = (char*)malloc(B64_ENCODE_LEN(file->body.file.size) + 1);
		memset(bodyBase64, 0, B64_ENCODE_LEN(file->body.file.size) + 1);
		base64_encode(file->body.file.text, file->body.file.size, bodyBase64);
		cJSON_AddStringToObject(fileJson, "code", bodyBase64);

		return TEEX_SUCCESS;

	} else {
		printf("Never print\n");
		return TEEX_ERROR_INTERNAL;
	}

}

teex_status_t jsonToFile(cJSON *fileJson, teexFile *file)
{
	int i = 0;
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;
	cJSON *item = NULL, *array = NULL, *arrayItem = NULL;
	teexFile *fileEntry = NULL;

	if ((item = cJSON_GetObjectItem(fileJson, "type")) != NULL
		&& cJSON_IsString(item)) {
		/* a legal files_json format*/	

		if (strcmp(item->valuestring, "DIR") == 0) {
			/* parse dir from json */
			
			file->type = TEEX_DIR;
			if ((item = cJSON_GetObjectItem(fileJson, "name")) != NULL
				&& cJSON_IsString(item) && item->valuestring) {
				
				if ((file->name = strdup(item->valuestring)) == NULL)
					return TEEX_ERROR_OUT_MEMORY;
				
			} else {
				TEEX_ERROR("Ilegal FileJson w/o 'name': %s\n", cJSON_Print(fileJson));
				return TEEX_ERROR_INVALID_JSON;
			}

			if ((array = cJSON_GetObjectItem(fileJson, "files")) != NULL) {
				
				file->body.dir.fileNumber = cJSON_GetArraySize(array);
				file->body.dir.files = (teexFile**)malloc(sizeof(teexFile*) * file->body.dir.fileNumber);
				if (file->body.dir.files == NULL)
					return TEEX_ERROR_OUT_MEMORY;

				memset(file->body.dir.files, 0, sizeof(teexFile*) * file->body.dir.fileNumber);

				for (i = 0; i < file->body.dir.fileNumber; i++) {
					
					if ((arrayItem = cJSON_GetArrayItem(array, i)) == NULL) {
						TEEX_ERROR("Ilegal FileJson w/o %dth item: %s\n", i, 
													cJSON_Print(fileJson));
						return TEEX_ERROR_INVALID_JSON;
					}
					
					if ((fileEntry = (teexFile*)malloc(sizeof(teexFile))) == NULL)
						return TEEX_ERROR_OUT_MEMORY;
					
					memset(fileEntry, 0, sizeof(teexFile));

					if ((retval = jsonToFile(arrayItem, fileEntry)) != TEEX_SUCCESS)
						return retval;

					file->body.dir.files[i] = fileEntry;
				}

			} else {
				TEEX_ERROR("Ilegal FileJson w/o 'files': %s\n", cJSON_Print(fileJson));
				return TEEX_ERROR_INVALID_JSON;
			}
		
			return TEEX_SUCCESS;

		} else if (strcmp(item->valuestring, "FILE") == 0) {
			/* parse file from json */

			file->type = TEEX_FILE;
			if ((item = cJSON_GetObjectItem(fileJson, "name")) != NULL
				&& cJSON_IsString(item) && item->valuestring) {
				if ((file->name = strdup(item->valuestring)) == NULL)
					return TEEX_ERROR_OUT_MEMORY;

			} else {
				TEEX_ERROR("Ilegal FileJson w/o 'name': %s\n", cJSON_Print(fileJson));
				return TEEX_ERROR_INVALID_JSON;
			}

			if ((item = cJSON_GetObjectItem(fileJson, "code")) != NULL
				&& cJSON_IsString(item) && item->valuestring) {
				
				file->body.file.size = B64_DECODE_LEN(strlen(item->valuestring));
				file->body.file.text = (unsigned char*)malloc(file->body.file.size + 1);
				memset(file->body.file.text, 0, file->body.file.size + 1);

				i = base64_decode(item->valuestring, file->body.file.text);
				file->body.file.size = i;
				file->body.file.text[i] = 0;

			} else {
				TEEX_ERROR("Ilegal FileJson w/o 'code': %s\n", cJSON_Print(fileJson));
				return TEEX_ERROR_INVALID_JSON;
			}

			return  TEEX_SUCCESS;

		} else {
			TEEX_ERROR("Ilegal file type %s\n", item->valuestring);
			return TEEX_ERROR_INTERNAL;
		}

	} else {
		TEEX_ERROR("Cannot get 'type' from json_str: %s\n", cJSON_Print(fileJson));
		return TEEX_ERROR_INVALID_JSON;
	}

	return TEEX_SUCCESS;
}


teex_status_t loadFile(char *fileName, teexFile *file)
{
	DIR *dir = NULL;
	FILE *fd = NULL;
	struct stat sb;
	struct dirent *entry = NULL;

	int n = 0;
	char newName[1024];
	teexFile *fileEntry = NULL;
	teex_status_t retval = TEEX_ERROR_UNIMPLEMENTED;

	if (fileName == NULL || file == NULL)
		return TEEX_ERROR_NULL_ARG;

	if (stat(fileName, &sb) >= 0 && S_ISDIR(sb.st_mode)) {
		
		file->type = TEEX_DIR;
		if ((file->name = strdup(fileName)) == NULL)
			return TEEX_ERROR_OUT_MEMORY;
		file->body.dir.fileNumber = 0;
		file->body.dir.files = NULL;

		if ((dir = opendir(fileName)) == NULL) {
			return TEEX_ERROR_INVALID_FILE;
		}

		while ((entry = readdir(dir)) != NULL) {
			/* skip the '.' and '..' directories */
			if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
				continue;
			
			memset(newName, 0, 1024);
			sprintf(newName, "%s/%s", fileName, entry->d_name);
			if ((fileEntry = (teexFile*)malloc(sizeof(teexFile))) == NULL)
				return TEEX_ERROR_OUT_MEMORY;
			
			memset(fileEntry, 0, sizeof(teexFile));

			retval = loadFile(newName, fileEntry);
			if (retval == TEEX_ERROR_UNHANDLED_FILE_TYPE)
				continue;
			
			if (retval != TEEX_SUCCESS) {
				free(fileEntry);
				return retval;
			}	
			
			file->body.dir.fileNumber ++;
			file->body.dir.files = (teexFile**)realloc(file->body.dir.files, 
										sizeof(teexFile*)*file->body.dir.fileNumber);
			file->body.dir.files[file->body.dir.fileNumber - 1] = fileEntry;
		}

	} else if (S_ISREG(sb.st_mode)) {
		
		file->type = TEEX_FILE;
		if ((file->name = strdup(fileName)) == NULL)
			return TEEX_ERROR_OUT_MEMORY;
		if ((fd = fopen(fileName, "r")) == NULL)
			return TEEX_ERROR_INVALID_FILE;


		file->body.file.size = sb.st_size;
		file->body.file.text = (unsigned char*)malloc(file->body.file.size + 1);
		memset(file->body.file.text, 0, file->body.file.size + 1);

		if ((n = fread(file->body.file.text, 1, file->body.file.size, fd)) <= 0) {
			free(file->body.file.text);
			return TEEX_ERROR_INVALID_FILE;
		}
		file->body.file.size = n;

		return TEEX_SUCCESS;
	} else {
		/* other file types are not handled */
		return TEEX_ERROR_UNHANDLED_FILE_TYPE;
	}
	
}
